'''
    Pegaso
    Copyright (c) 2012 Dario Tognacci

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''

from structures import *
import util

# The function in current processing
current_function = None


def create_client_function(function):
    global current_function
    current_function = function
    fun_name = function['identifier']
    code = []

    # Get the parameters representation (comma separeted)
    param_repr = []
    for param in function['parameters']:
        param_repr.append(param.get_str_representation())
    param_repr = ", ".join(param_repr)

    # The function signature
    code.append("{0} {1}({2}) {{".format(function['return_type'],
                                        function['identifier'], param_repr))
    
    code.append("#ifdef DEBUG")
    code.append("printf(\"{0}\\n\");".format(fun_name))
    code.append("fflush(stdout);")
    code.append("#endif")
    
    code.append("R_{0}_Param in;".format(function['identifier']))

    # Wrapping code
    code.append("// Wrapping")
    for param in function['parameters']:
        iter = wrap_client_side(param, "", "", True)
        for item in iter:
            code.append(item)
    code.append("// End Wrapping")

    # Remote calling via RPC
    code.append("//Remote calling")
    code.append("R_{0}_Param *out = r_{1}_{2}(&in,cl);".format(
                function['identifier'], function['identifier'].lower(),
                util.version))
    code.append("if (out == NULL) {")
    code.append("char *str_error = \"Error during remote call {0}.\";".format(
                function['identifier']))
    code.append("clnt_pcreateerror(str_error);")
    code.append("return CKR_GENERAL_ERROR;")
    code.append("}")
    
    # Free wrapping code
    code.append("// Free Wrapping")
    for param in function['parameters']:
        iter = free_wrap_client_side(param, "", "", True)
        for item in iter:
            code.append(item)
    code.append("// End Free Wrapping")

    # Unwrapping code
    code.append("// Unwrapping")
    for param in function['parameters']:
        if param['modifiable']:
            iter = unwrap_client_side(param, "", "", True)
            for item in iter:
                code.append(item)
    code.append("// End Unwrapping")

    code.append("return out->return_value;")
    code.append("}")

    return code


def free_wrap_client_side(param, str_nested, op, first_level):
    code = []
    nested_name = str_nested + op + param['identifier']
    par_id = param['identifier']
    
    if isinstance(param, ParameterType):
        aux_param = statements_table[param['type']]
        aux_param['identifier'] = param['identifier']
        aux_param['trasform_ptr'] = param['trasform_ptr']
        aux_param['size_of'] = param['size_of']
        return free_wrap_client_side(aux_param, "", "", True)
    elif isinstance(param, FunctionType):
        return []

    if isinstance(param, SimpleType):

        if param['trasform_ptr'] is not None:
            # Array dinamico
            if isinstance(statements_table[param['type']], SimpleType): #ok
                pass
            else:
                code.append("if ({0} != NULL) {{".format(nested_name))
                code.append("REMOTE_{0} *pRemote = in.{1}.{2}_val;".format(param['type'], nested_name, par_id))
                if param['size_of'].pointer is True:
                    code.append("free_wrap_{0}(pRemote, (int) *{1});".format(param['type'], param['size_of'].variable))
                else:
                    code.append("free_wrap_{0}(pRemote, (int) {1});".format(param['type'], param['size_of'].variable))
                code.append("}")
                
            code.append("free(in.{0}.{1}_val);".format(nested_name, par_id))
            code.append("in.{0}.{1}_val = NULL;".format(nested_name, par_id))
        
        elif param['pointer'] and param['type'] in statements_table.keys():
            # Puntatore
            if isinstance(statements_table[param['type']], SimpleType): #ok
                pass
            else: #ok
                code.append("if (in.{0} != NULL) {{".format(nested_name)) ## this added
                pointed_struct = statements_table[param['type']]
                for field in pointed_struct['fields']:
                    code += free_wrap_client_side(field, nested_name, "->", field['identifier'])
                code.append("}") ## this added
                code.append("free(in.{0});".format(nested_name))
                code.append("in.{0} = NULL;".format(nested_name))
        
        else:
            # Tipo semplice
            if param['array']: #ok
                pass
            elif isinstance(statements_table[param['type']], StructType): #ok
                aux = statements_table[param['type']]
                for field in aux['fields']:
                    code += free_wrap_client_side(field, nested_name, ".", False)
            else: #ok
                pass
    
    elif isinstance(param, StructType):
        for field in param['fields']:
            code += free_wrap_client_side(field, nested_name, ".", False)

    return code



def wrap_client_side(param, str_nested, op, first_level):
    code = []
    nested_name = str_nested + op + param['identifier']
    par_id = param['identifier']
    
    if isinstance(param, ParameterType):
        aux_param = statements_table[param['type']]
        aux_param['identifier'] = param['identifier']
        aux_param['trasform_ptr'] = param['trasform_ptr']
        aux_param['size_of'] = param['size_of']
        return wrap_client_side(aux_param, "", "", True)
    elif isinstance(param, FunctionType):
        return []

    if isinstance(param, SimpleType):

        if param['trasform_ptr'] is not None:
            # Array dinamico
            
            # Let see if dimension is expressed with a number
            size = None
            try:
                size = int(param['size_of'].variable)
            except ValueError:
                size = None
            if size is None:
                if param['size_of'].pointer is True:
                    size = "{0}{1}(*{2})".format(str_nested, op, param['size_of'].variable)
                else:
                    size = "{0}{1}{2}".format(str_nested, op, param['size_of'].variable)
            
            code.append("if ({0} == NULL) {{".format(nested_name))
            code.append("in.{0}.{1}_val = NULL;".format(nested_name, par_id))
            code.append("in.{0}.{1}_len = 0;".format(nested_name, par_id))
            code.append("}")
            code.append("else {")
            code.append("in.{0}.{1}_val = malloc(sizeof(REMOTE_{2}) * {3});".format(nested_name, par_id, param['type'], size))
            
            if isinstance(statements_table[param['type']], SimpleType):
                code.append("memcpy(in.{0}.{3}_val, {0}, sizeof({1}) * {2});".format(nested_name, param['type'], size, par_id))
            else:
                code.append("{0} *pLocal = {1};".format(param['type'], nested_name))
                code.append("REMOTE_{0} *pRemote = in.{1}.{2}_val;".format(param['type'], nested_name, par_id))
                if param['size_of'].pointer is True:
                    code.append("wrap_{0}(pLocal, pRemote, {1});".format(param['type'], param['size_of'].variable))
                else:
                    code.append("wrap_{0}(pLocal, pRemote, &{1});".format(param['type'], param['size_of'].variable))
            
            code.append("in.{0}.{1}_len = {2};".format(nested_name, par_id, size))
            code.append("}")
        
        elif param['pointer'] and param['type'] in statements_table.keys():
            # Puntatore
            if isinstance(statements_table[param['type']], SimpleType):
                code.append("in.{0} = (void*) {1};".format(nested_name, par_id))
                
            else:
                code.append("in.{0} = malloc(sizeof(REMOTE_{1}));".format(nested_name, param['type']))
                pointed_struct = statements_table[param['type']]
                for field in pointed_struct['fields']:
                    code += wrap_client_side(field, nested_name, "->", field['identifier'])
        
        else:
            # Tipo semplice
            if param['array']:
                code.append("memcpy(in.{0}, {0}, {1});".format(nested_name, param['array_dimension']))
            elif isinstance(statements_table[param['type']], StructType):
                aux = statements_table[param['type']]
                for field in aux['fields']:
                    code += wrap_client_side(field, nested_name, ".", False)
            else:
                code.append("in.{0} = {0};".format(nested_name))
    
    elif isinstance(param, StructType):
        for field in param['fields']:
            code += wrap_client_side(field, nested_name, ".", False)

    return code


def unwrap_client_side(param, str_nested, op, first_level):
    code = []
    nested_name = str_nested + op + param['identifier']
    par_id = param['identifier']

    if isinstance(param, ParameterType):
        aux_param = statements_table[param['type']]
        aux_param['identifier'] = param['identifier']
        aux_param['trasform_ptr'] = param['trasform_ptr']
        aux_param['size_of'] = param['size_of']
        return unwrap_client_side(aux_param, "", "", True)
    elif isinstance(param, FunctionType):
        return []

    if isinstance(param, SimpleType):

        if param['trasform_ptr']:
            # Array dinamico
            code.append("if (out->{0}.{1}_len != 0) {{".format(nested_name, par_id))
            
            if isinstance(statements_table[param['type']], SimpleType):
                aux_id = statements_table[param['type']]['type']
                code.append("memcpy({0}, out->{0}.{1}_val, sizeof({2}) * out->{0}.{1}_len);".format(nested_name, par_id, aux_id))
            else:
                code.append("{0} *pLocal = {1};".format(param['type'], nested_name))
                code.append("REMOTE_{0} *pRemote = out->{1}.{2}_val;".format(param['type'], nested_name, par_id))
                if param['size_of'].pointer is True:
                    code.append("unwrap_{0}(pLocal, pRemote, {1});".format(param['type'], param['size_of'].variable))
                else:
                    code.append("unwrap_{0}(pLocal, pRemote, &{1});".format(param['type'], param['size_of'].variable))
            
            code.append("}")
        
        elif param['pointer'] and param['type'] in statements_table.keys():
            # Puntatore
            code.append("if ({0} != NULL && out->{0} != NULL) {{".format(nested_name))
            
            if isinstance(statements_table[param['type']], SimpleType):
                code.append("memcpy({0}, out->{0}, sizeof({1}));".format(nested_name, param['type']))
            else:
                pointed_struct = statements_table[param['type']]
                for field in pointed_struct['fields']:
                    code += unwrap_client_side(field, nested_name, "->", False)
                
            code.append("}")
        
        else:
            # Tipo semplice
            if param['array']:
                code.append("memcpy({0}, out->{0}, {1});".format(nested_name,
                            param['array_dimension']))
            elif isinstance(statements_table[param['type']], StructType):
                aux = statements_table[param['type']]
                for field in aux['fields']:
                    code += unwrap_client_side(field, nested_name, ".", False)
            else:
                code.append("{0} = out->{0};".format(nested_name))
    
    elif isinstance(param, StructType):
        for field in param['fields']:
            code += unwrap_client_side(field, nested_name, ".", False)

    return code


def create_server_function(function):
    global current_function
    current_function = function
    fun_name = function['identifier']
    code = []

    # The function signature
    code.append("R_{0}_Param * r_{1}_{2}(R_{0}_Param *param, CLIENT *cl) {{".format(
                fun_name, fun_name.lower(), util.version))
    code.append("}")
    code.append("R_{0}_Param * r_{1}_{2}_svc(R_{0}_Param *in, struct svc_req *req) {{".format(
                fun_name, fun_name.lower(), util.version))
    code.append("#ifdef DEBUG")
    code.append("printf(\"{0}\\n\");".format(fun_name))
    code.append("fflush(stdout);")
    code.append("#endif")
    
    code.append("static R_{0}_Param out;".format(fun_name))
    
    # Declarations of static variables
    for param in function['parameters']:
        if not param['pointer']:
            code.append("static {0} {1};".format(param['type'], param['identifier']))
        else:
            code.append("static {0} *{1};".format(param['type'], param['identifier']))
    
    # Free Unwrapping
    code.append("// Free Unwrapping")
    for param in function['parameters']:
        iter = free_unwrap_server_side(param, "", "", True)
        for item in iter:
            code.append(item)
    code.append("// End Free Unwrapping")
    
    # Unwrapping code
    code.append("// Unwrapping")
    for param in function['parameters']:
        iter = unwrap_server_side(param, "", "", True)
        for item in iter:
            code.append(item)
    code.append("// End Unwrapping")

    # Get the parameters representation (comma separeted)
    param_repr = []
    for parameter in function['parameters']:
        if isinstance(parameter, ParameterType):
            param_repr.append("%s" % parameter['identifier'])
        elif isinstance(parameter, FunctionType):
            param_repr.append("NULL")
    param_repr = ",".join(param_repr)

    # Remote calling via RPC
    code.append("//Remote calling")
    code.append("{0} res = {1}({2});".format(function['return_type'],
                                             fun_name[3:], param_repr))

    # Free Wrapping
    code.append("// Free Wrapping")
    for param in function['parameters']:
        iter = free_wrap_server_side(param, "", "", True)
        for item in iter:
            code.append(item)
    code.append("// End Free Wrapping")

    # Wrapping code
    code.append("// Wrapping")
    for param in function['parameters']:
        iter = wrap_server_side(param, "", "", True)
        for item in iter:
            code.append(item)
    code.append("// End Wrapping")

    code.append("out.return_value = res;")
    code.append("return &out;")
    code.append("}")

    return code


def wrap_server_side(param, str_nested, op, first_level):
    code = []
    nested_name = str_nested + op + param['identifier']
    par_id = param['identifier']
    
    if isinstance(param, ParameterType):
        aux_param = statements_table[param['type']]
        aux_param['identifier'] = param['identifier']
        aux_param['trasform_ptr'] = param['trasform_ptr']
        aux_param['size_of'] = param['size_of']
        return wrap_server_side(aux_param, "", "", True)
    elif isinstance(param, FunctionType):
        return []

    if isinstance(param, SimpleType):

        if param['trasform_ptr']:
            # Array dinamico
            
            # Let see if dimension is expressed with a number
            size = None
            try:
                size = int(param['size_of'].variable)
            except ValueError:
                size = None
            if size is None:
                if param['size_of'].pointer is True:
                    size = "{0}{1}(*{2})".format(str_nested, op,
                                                 param['size_of'].variable)
                else:
                    size = "{0}{1}{2}".format(str_nested, op,
                                              param['size_of'].variable)
            
            code.append("if ({0} == NULL) {{".format(nested_name))
            code.append("out.{0}.{1}_val = NULL;".format(nested_name, par_id))
            code.append("out.{0}.{1}_len = 0;".format(nested_name, par_id))
            code.append("}")
            code.append("else {")
            code.append("out.{0}.{1}_val = malloc(sizeof(REMOTE_{2}) * {3});".format(nested_name, par_id, param['type'], size))
            code.append("out.{0}.{1}_len = {2};".format(nested_name, par_id, size))
            
            if isinstance(statements_table[param['type']], SimpleType):
                code.append("memcpy(out.{0}.{1}_val, {0}, sizeof({2}) * {3});".format(nested_name, par_id, param['type'], size))
            else:
                code.append("{0} *pLocal = {1};".format(param['type'], nested_name))
                code.append("REMOTE_{0} *pRemote = out.{1}.{2}_val;".format(param['type'], nested_name, par_id))
                if param['size_of'].pointer is True:
                    code.append("wrap_{0}(pLocal, pRemote, {1});".format(param['type'], param['size_of'].variable))
                else:
                    code.append("wrap_{0}(pLocal, pRemote, &{1});".format(param['type'], param['size_of'].variable))
            
            code.append("}")
        
        elif param['pointer'] and param['type'] in statements_table.keys():
            # Puntatore
            if isinstance(statements_table[param['type']], SimpleType):
                code.append("out.{0} = {0};".format(nested_name, param['type']))
            else:
                code.append("if ({0} == NULL) {{".format(nested_name))
                code.append("out.{0} = NULL;".format(nested_name))
                code.append("}")
                code.append("else {")
                code.append("out.{0} = malloc(sizeof(REMOTE_{1}));".format(nested_name, param['type']))
                pointed_struct = statements_table[param['type']]
                for field in pointed_struct['fields']:
                    code += wrap_server_side(field, nested_name, "->", False)
                code.append("}")
        
        else:
            # Tipo semplice
            if param['array']:
                code.append("memcpy(out.{0}, {0}, {1});".format(nested_name, param['array_dimension']))
            elif isinstance(statements_table[param['type']], StructType):
                aux = statements_table[param['type']]
                for field in aux['fields']:
                    code += wrap_server_side(field, nested_name, ".", False)
            else:
                code.append("out.{0} = {0};".format(nested_name))
    
    elif isinstance(param, StructType):
        for field in param['fields']:
            code += wrap_server_side(field, nested_name, ".", False)

    return code


def free_wrap_server_side(param, str_nested, op, first_level):
    code = []
    nested_name = str_nested + op + param['identifier']
    par_id = param['identifier']
    
    if isinstance(param, ParameterType):
        aux_param = statements_table[param['type']]
        aux_param['identifier'] = param['identifier']
        aux_param['trasform_ptr'] = param['trasform_ptr']
        aux_param['size_of'] = param['size_of']
        return free_wrap_server_side(aux_param, "", "", True)
    elif isinstance(param, FunctionType):
        return []

    if isinstance(param, SimpleType):

        if param['trasform_ptr']:
            # Array dinamico
            if isinstance(statements_table[param['type']], SimpleType): #ok
                pass
            else: #ok
                code.append("if (out.{0}.{1}_val != NULL) {{".format(nested_name, par_id))
                code.append("REMOTE_{0} *pRemote = out.{1}.{2}_val;".format(param['type'], nested_name, par_id))
                code.append("free_wrap_{0}(pRemote, (int) out.{1}.{2}_len);".format(param['type'], nested_name, par_id))
                code.append("}")
            
            code.append("free(out.{0}.{1}_val);".format(nested_name, par_id))
            code.append("out.{0}.{1}_val = NULL;".format(nested_name, par_id))
        
        elif param['pointer'] and param['type'] in statements_table.keys():
            # Puntatore
            if isinstance(statements_table[param['type']], SimpleType): #ok
                pass
            else: #ok
                code.append("if (out.{0} != NULL) {{".format(nested_name)) ## this added
                pointed_struct = statements_table[param['type']]
                for field in pointed_struct['fields']:
                    code += free_wrap_server_side(field, nested_name, "->", False)
                code.append("}") ## this added
                code.append("free(out.{0});".format(nested_name))
                code.append("out.{0} = NULL;".format(nested_name))
        
        else:
            # Tipo semplice
            if param['array']: #ok
                pass
            elif isinstance(statements_table[param['type']], StructType): #ok
                aux = statements_table[param['type']]
                for field in aux['fields']:
                    code += free_wrap_server_side(field, nested_name, ".", False)
            else: #ok
                pass
    
    elif isinstance(param, StructType):
        for field in param['fields']:
            code += free_wrap_server_side(field, nested_name, ".", False)

    return code


def unwrap_server_side(param, str_nested, op, first_level):
    code = []
    nested_name = str_nested + op + param['identifier']
    par_id = param['identifier']

    if isinstance(param, ParameterType):
        aux_param = statements_table[param['type']]
        aux_param['identifier'] = param['identifier']
        aux_param['trasform_ptr'] = param['trasform_ptr']
        aux_param['size_of'] = param['size_of']
        return unwrap_server_side(aux_param, "", "", True)
    elif isinstance(param, FunctionType):
        return []

    if isinstance(param, SimpleType):

        if param['trasform_ptr']:
            # Array dinamico
            new_type = param['trasform_ptr'].type
            code.append("if (in->{0}.{1}_len == 0) {{".format(nested_name, par_id))
            code.append("{0} = NULL;".format(nested_name))
            code.append("}")
            code.append("else {")
            code.append("{0} = malloc(sizeof({1}) * in->{0}.{2}_len);".format(nested_name, new_type, par_id))
            
            if isinstance(statements_table[param['type']], SimpleType):
                code.append("memcpy({0}, in->{0}.{1}_val, sizeof({2}) * in->{0}.{1}_len);".format(nested_name, par_id, new_type))
            else:
                code.append("{0} *pLocal = {1};".format(param['type'], nested_name))
                code.append("REMOTE_{0} *pRemote = in->{1}.{2}_val;".format(param['type'], nested_name, par_id))
                code.append("unwrap_{0}(pLocal, pRemote, &(in->{1}));".format(param['type'], param['size_of'].variable))
            
            code.append("}")
        
        elif param['pointer'] and param['type'] in statements_table.keys():
            # Puntatore
            code.append("if (in->{0} == NULL) {{".format(nested_name))
            code.append("{0} = NULL;".format(nested_name))
            code.append("}")
            code.append("else {")
            code.append("{0} = malloc(sizeof({1}));".format(nested_name, param['type']))
            
            if isinstance(statements_table[param['type']], SimpleType): 
                code.append("memcpy({0}, in->{0}, sizeof({1}));".format(nested_name, param['type']))
            else:
                pointed_struct = statements_table[param['type']]
                for field in pointed_struct['fields']:
                    code += unwrap_server_side(field, nested_name, "->", False)
            
            code.append("}")
        
        else:
            # Tipo semplice
            if param['array']:
                code.append("memcpy({0}, in->{0}, {1});".format(nested_name, param['array_dimension']))
            elif isinstance(statements_table[param['type']], StructType):
                aux = statements_table[param['type']]
                for field in aux['fields']:
                    code += unwrap_server_side(field, nested_name, ".", False)
            else:
                code.append("{0} = in->{0};".format(nested_name))
    
    elif isinstance(param, StructType):
        for field in param['fields']:
            code += unwrap_server_side(field, nested_name, ".", False)

    return code

def free_unwrap_server_side(param, str_nested, op, first_level):
    code = []
    nested_name = str_nested + op + param['identifier']
    par_id = param['identifier']

    if isinstance(param, ParameterType):
        aux_param = statements_table[param['type']]
        aux_param['identifier'] = param['identifier']
        aux_param['trasform_ptr'] = param['trasform_ptr']
        aux_param['size_of'] = param['size_of']
        return free_unwrap_server_side(aux_param, "", "", True)
    elif isinstance(param, FunctionType):
        return []

    if isinstance(param, SimpleType):

        if param['trasform_ptr']:
            # Array dinamico
            if isinstance(statements_table[param['type']], SimpleType): #ok
                pass
            else:
                code.append("if ({0} != NULL) {{".format(nested_name))
                code.append("{0} *pLocal = {1};".format(param['type'], nested_name))
                if param['size_of'].pointer is True:
                    code.append("free_unwrap_{0}(pLocal, (int) *{1});".format(param['type'], param['size_of'].variable))
                else:
                    code.append("free_unwrap_{0}(pLocal, (int) {1});".format(param['type'], param['size_of'].variable))
                code.append("}")
            
            code.append("free({0});".format(nested_name))
            code.append("{0} = NULL;".format(nested_name))
        
        elif param['pointer'] and param['type'] in statements_table.keys():
            # Puntatore
            if isinstance(statements_table[param['type']], SimpleType): #ok
                pass
            else: #ok
                code.append("if ({0} != NULL) {{".format(nested_name)) ## this added
                pointed_struct = statements_table[param['type']]
                for field in pointed_struct['fields']:
                    code += free_unwrap_server_side(field, nested_name, "->", False)
                code.append("}") ## this added
            
            code.append("free({0});".format(nested_name))
            code.append("{0} = NULL;".format(nested_name))
        
        else:
            # Tipo semplice
            if param['array']: #ok
                pass
            elif isinstance(statements_table[param['type']], StructType): #ok
                aux = statements_table[param['type']]
                for field in aux['fields']:
                    code += free_unwrap_server_side(field, nested_name, ".", False)
            else: #ok
                pass
    
    elif isinstance(param, StructType): #ok
        for field in param['fields']:
            code += free_unwrap_server_side(field, nested_name, ".", False)

    return code